Release 10.1A: OpenEdge Development:
Progress 4GL Handbook


Defining triggers

The most basic way to define a trigger is to put the trigger definition directly into the object definition:

DEFINE BUTTON BtnQuit LABEL "Quit" 
     TRIGGERS: 
       ON CHOOSE  
         QUIT. 
     END TRIGGERS. 

The TRIGGERS block in a DEFINE statement can contain one or more individual trigger definitions, each starting with an ON phrase naming the event followed by a single statement or a DO-END block of statements. This form is called a definitional trigger.

If you use the AppBuilder to create your application procedures, it always creates separate blocks of executable code that attach triggers to objects at run time. This form is called a run-time trigger. There is no inherent advantage to one form over the other. Either way, the code in the trigger block is compiled and turned into r-code along with the rest of the procedure. You should use the AppBuilder to organize your trigger blocks to provide a more readable structure to your procedures.

The form of the trigger block for a run-time trigger names both the event and the object it applies to:

ON event-name [ , . . .] OF object-name [ , . . .] statements 

Either the event-name or the object-name (or both) can be a comma-separated list. The statements can be either a single statement or a DO-END block of statements.

Setting up triggers to be executed

A trigger is executed if the object it applies to is enabled (sensitive), and if the trigger is currently active and in scope.

The trigger is active if the statement that defines it has been executed. As discussed earlier in the book, Progress processes statements in a procedure in the order it encounters them in. Thus, the definition of an object must come before the block of code that defines a trigger for the object. Otherwise, Progress won’t recognize the object reference. And the trigger definition must come before the user receives control and can perform the action that would fire the event and run the trigger.

The scope of a definitional trigger is the scope of the object it’s defined for. The scope of a run-time trigger is the nearest containing procedure or trigger block where it is defined. So if a trigger definition is inside an internal procedure, then its scope is limited to that internal procedure. If it’s outside of any internal procedure, its scope is the entire external procedure.

The AppBuilder places all trigger definitions toward the top of the procedure, following the Definitions section of the procedure but before the main block and all internal procedures. In this way the trigger blocks are scoped to the entire procedure and they are made active before any user actions that invoke them can occur.

To build a very simple example procedure to demonstrate some of the rules of run-time trigger processing in Progress:

  1. Define a button and give it an initial label, then define an INTEGER variable as a counter:
  2. DEFINE BUTTON bButton LABEL "Initial Label". 
    DEFINE VARIABLE iCount AS INTEGER     NO-UNDO. 
    

  3. Add a statement to enable the button in a frame. As you learned earlier, this causes both the button and its frame to be instantiated and realized:
  4. ENABLE bButton WITH FRAME fFrame. 
    

  5. Define a run-time trigger for the button that changes its label so that you can see that the trigger fired:
  6. ON CHOOSE OF bButton IN FRAME fFrame 
    DO: 
         iCount = iCount + 1. 
          bButton:LABEL IN FRAME fFrame = "External " + STRING(iCount). 
    END. 
    

  7. Create a WAIT-FOR statement that blocks the termination of the procedure and allows the user to click the button:
  8. WAIT-FOR CLOSE OF THIS-PROCEDURE. 
    

  9. Run the procedure. As you would expect, the button’s label is changed when you click the button:
  10. The ON CHOOSE trigger you defined is scoped to the entire procedure and executes each time the button is pressed.

To define another trigger for the button in an internal procedure, make these changes to the procedure:

DEFINE BUTTON bButton LABEL "Initial Label". 
DEFINE VARIABLE iCount AS INTEGER     NO-UNDO. 
ENABLE bButton WITH FRAME fFrame. 
ON CHOOSE OF bButton IN FRAME fFrame 
DO: 
     iCount = iCount + 1. 
     bButton:LABEL IN FRAME fFrame = "External " + STRING(iCount). 
END.  
RUN ChooseProc. 
WAIT-FOR CLOSE OF THIS-PROCEDURE. 
PROCEDURE ChooseProc. 
     ON CHOOSE OF bButton IN FRAME fFrame 
     DO: 
          iCount = iCount + 1. 
          bButton:LABEL In FRAME fFrame = "Internal " + STRING(iCount). 
     END. 
END. 

Before the WAIT-FOR statement the code runs the internal procedure ChooseProc. This procedure defines a different trigger for the same button, which identifies the trigger with the label Internal. The second trigger definition replaces the first one within the ChooseProc procedure.

Note that the scope of the button is the entire external procedure because it is defined at that level. The scope of the variable iCount is also the external procedure for the same reason. But what is the scope of the second trigger definition you just added?

It is scoped only to the internal procedure where it is defined. So what happens when you run this version of the procedure? Before you run it, walk through the code in your head to decide what happens.

The external procedure defines a trigger for the button. It then runs an internal procedure that defines a different trigger. That internal procedure then exits, without giving the user any chance to use the new trigger, and its trigger goes out of scope. So what happens when the user clicks the button?

Figure 8–9: Result of trigger example procedure

Figure 8–9 shows that Progress reverts to the original trigger. This example shows you that Progress effectively keeps a stack of trigger definitions. If a later definition goes out of scope, Progress reverts to the trigger definition that is now back in scope (if there is one).

So how would you see the effect of the internal procedure trigger? You can place a WAIT-FOR statement inside the internal procedure to force Progress to wait until the user clicks the button.

To try this, add this statement to the end of the ChooseProc procedure:

WAIT-FOR CHOOSE OF bButton. 

Note that you can wait for any event, not just the close of the procedure or its window. This statement waits until the user clicks the button exactly once. Then the WAIT-FOR is satisfied, the internal procedure exits, and the first trigger takes over.

To see the result of each button press, run the procedure again. Figure 8–10 shows the result of the first four button presses.

Figure 8–10: Results of running the ChooseProc procedure

Making a trigger persistent

Adding the second WAIT-FOR statement is very awkward, and it is pretty close to an absolute rule in Progress that your entire application, not just a single procedure, should have only a single WAIT-FOR statement. This rule is summarized in the saying: “One world, one WAIT-FOR.” Multiple WAIT-FOR statements can easily get tangled up in each other if they are not satisfied in the exact reverse order from the order they are defined in, and this can result in a WAIT-FOR that doesn’t terminate properly.

So how else do you establish a trigger inside an internal procedure or a trigger block, or for that matter inside another external procedure that you call, and have that trigger persist beyond the scope of its declaration?

Progress provides a special statement to let you do this:

PERSISTENT RUN procedure-name [ (input-parameters ) ]. 

The persistent trigger definition can have only this one RUN statement (not a DO-END block with any other statements). You can pass optional INPUT parameters to the procedure you run but no OUTPUT or INPUT-OUTPUT parameters. If you pass parameters, the parameter values are evaluated once when the trigger is defined. They are not re-evaluated each time the trigger executes.

To see how you write a persistent trigger and what its effects are, change the ChooseProc procedure and add the new procedure PersistProc, as follows:

PROCEDURE ChooseProc. 
    ON CHOOSE OF bButton IN FRAME fFrame 
        PERSISTENT RUN PersistProc. 
END PROCEDURE. 
PROCEDURE PersistProc: 
        iCount = iCount + 1. 
        bButton:LABEL IN FRAME fFrame = "Internal " + STRING(iCount). 
END PROCEDURE. 

Now when you run the procedure and click the button you get a very different result.
Figure 8–11 shows the results of the first three button presses.

Figure 8–11: Results of running the PersistProc procedure

Once you have established the persistent trigger, it remains in effect as long as the object it’s associated with exists, just as a definitional trigger would.

Using the REVERT statement to cancel a trigger

An ON statement can contain the single keyword REVERT to cancel an existing trigger definition before it goes out of scope:

ON events OF objects REVERT. 

The REVERT statement cancels any run-time trigger for the event and object defined in the current procedure or trigger. Note that you cannot use REVERT to cancel a persistent trigger. Instead, you must run another persistent trigger procedure that either defines a new trigger or consists of just a RETURN statement.

Defining triggers to fire anywhere

You can use the ANYWHERE option of the ON statement to set up a trigger that applies to all objects in an application. Use the following syntax:

ON events ANYWHERE statement or code block. 


Copyright © 2005 Progress Software Corporation
www.progress.com
Voice: (781) 280-4000
Fax: (781) 280-4095